
#include <wx/sstream.h>
#include <wx/file.h>
#include <wx/filename.h>

#include "MsnSession.h"
#include "MsnEvent.h"
#include "MsnCommand.h"
#include "MsnMessenger.h"
#include "MsnSession.h"
#include "FileTransferConnection.h"
#include "SessionConnection.h"

#include "FileTransferManager.h"
#include "FileTransferEntry.h"

FileTransferConnection::FileTransferConnection(MsnSession* _session, const wxString& _inviteCookie,  const wxString& _authCookie)
		:session(_session),cookie(_inviteCookie),authCookie(_authCookie),cancelled(false), serverSocket(0)
{
	
}

int FileTransferConnection::OpenServerSocket(int port)
{
    wxIPV4address addr;
    addr.Service(port);

    DEBUG(_T("creating socket server "));

    serverSocket = new wxSocketServer(addr);
    while (!serverSocket->Ok()){
		if (serverSocket->LastError() == wxSOCKET_INVPORT){
			DEBUG(wxT("Port in use"));
			serverSocket->Destroy(); 
			port++;
			addr.Service(port);
			serverSocket = new wxSocketServer(addr);
		}else{
			DEBUG(wxT("Create socket server error"));
			serverSocket->Destroy();
			return -1;
		}       
    }

    return port;
}

bool FileTransferConnection::Connect(const wxString& hostname,int port)
{
	if(serverSocket != NULL){ //work as server
		DEBUG(wxString::Format(_T("waiting for incoming connection")));

		if(serverSocket->WaitForAccept(30)){
			socket = serverSocket->Accept();
			socket->SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK );
			DEBUG(_T("connection established "));
			serverSocket->Destroy();
			return true;
		}else{
			//no imconming connection
			DEBUG(_T("connection timeout "));

			//TODO 
			//session->OnFileTransferTimeout(cookie);

			serverSocket->Destroy();
			return false;
		}	
	}else{
		//work as client
		return MsnConnection::Connect(hostname, port);
	}
}


void FileTransferConnection::Cancel()
{
	cancelled = true;
	Close();
}


FtpFileSender::FtpFileSender(MsnSession* _session, const wxString& _inviteCookie,  const wxString& _authCookie)
	:FileTransferConnection(_session, _inviteCookie, _authCookie)
{

}


void FtpFileSender::ProcessCommand(const wxString& line)
{
	if(line.StartsWith(_T("VER"))){
		SendCommand(_T("VER MSNFTP\r\n"));	
	}else if(line.StartsWith(_T("USR"))){
		wxFileName file(filePath);
        wxString size = file.GetSize().ToString();
        SendCommand(_T("FIL ") + size +_T("\r\n"));		
	}else if(line.StartsWith(_T("TFR"))){
		SendFile();
	}
}

void FtpFileSender::SendFile()
{
    DEBUG(_T("send file content"));

	wxString filePath = session->GetFtManager()->GetEntry(cookie)->GetFilePath();
    wxFile file;

    if(!file.Open(filePath, wxFile::read)){
        Close();
        return;
    }

    wxFileName fileName(filePath);

	long fileSize = fileName.GetSize().GetValue();
	long fileRead = 0;
	long notify = 0;

	char header[3];
    char buf[2045];

    int actualRead = file.Read(buf, 2045);

    while(actualRead > 0){

		if(cancelled){
			DEBUG(_T("file transfer cancelled"));
			break;
		}

        header[0] = 0;
        header[1] = actualRead % 256;
        header[2] = actualRead / 256;

        socket->Write(header, 3);
        socket->Write(buf, actualRead);

        if(socket->Error()){
            wxSocketError err = socket->LastError();
            DEBUG(wxString::Format(_T("socket write error: %d"), err));

       //     DEBUG(_("connection error 2222 "));
            break;
        }

		notify += actualRead;
		fileRead += actualRead;

		if(notify*100 > fileSize || fileRead == fileSize){
			DEBUG(wxString::Format(_T("notify :%d"),notify));
			//session->OnFileTransferUpdated(cookie, notify);
			notify = 0;
		}

        actualRead = file.Read(buf, 2045);
    }

    if(!socket->Error() && !cancelled){
        header[0] = 0;
        header[1] = 0;
        header[2] = 0;
        socket->Write(header, 3);
    }

	if(fileRead == fileSize){
		DEBUG(_T("sending file finished"));
		///session->OnFileTransferDone(cookie);
		return;
	}

	if(fileRead < fileSize && cancelled != true){
		DEBUG(wxString::Format(_T("send(%d) < file size(%d)"), fileRead, fileSize));
		///session->OnFileTransferFailed(cookie);
	}
}



FtpFileReceiver::FtpFileReceiver(MsnSession* _session, const wxString& _inviteCookie,  const wxString& _authCookie)
	:FileTransferConnection(_session, _inviteCookie, _authCookie)
{

}

void FtpFileReceiver::ConnectionOpened()
{
    SendCommand(_T("VER MSNFTP\r\n"));	
}

void FtpFileReceiver::ProcessCommand(const wxString& line)
{
	if(line.StartsWith(_T("VER"))){
		wxString usr;
		usr<<_T("USR ")<<session->GetMessenger()->GetUserPassport()<<_T(" ")<<authCookie<<_T("\r\n");
		SendCommand(usr);
	}else if(line.StartsWith(_T("FIL"))){  
		this->SendCommand(_T("TFR\r\n"));
		long fileSize = StringUtil::ToLong(line.After(' '));
		
		ReceiveFile(fileSize);
	}
    else{}	
}

void FtpFileReceiver::ReceiveFile(long fileSize)
{
    wxFile file;

    if(!file.Open(filePath, wxFile::write)){
        Close();

        ///todo handle error
        return;
    }

	long fileRead = 0;
	long notify = 0;

	char header[3] = {0};
	char blockBuf[2048] = {0};

    while(fileRead < fileSize){

		if(cancelled){
			DEBUG(_T("file transfer cancelled"));
			break;
		}

		 if(socket->Error()){
            DEBUG(_T("socket error occured !!!!! "));
            break;
        }

        ReadChunk(header, 3);

		if(header[0] == 1){
			DEBUG(_T("reach end of data !!!!! "));
			break;
		}		

        int blockSize = (int)(header[1]&0xff) + (int)(header[2]&0xff)*256;

		//DEBUG(wxString::Format(_T("head=%d block=%d"), header[1], blockSize));

        //char* blockBuf = new char[blockSize];
        ReadChunk(blockBuf, blockSize);
        file.Write(blockBuf, blockSize);

		fileRead += blockSize;
		notify += blockSize;

		if(notify*100 > fileSize || fileRead == fileSize){	
			
			file.Flush();
			///session->OnFileTransferUpdated(cookie, notify);
			notify = 0;
		}

    }

    file.Close();

	if(fileRead == fileSize){
		DEBUG(_T("receiving file finished"));
		this->SendCommand(_T("BYE 16777989\r\n"));

		//file transfer done
		///session->OnFileTransferDone(cookie);
		return;
	}

	if(fileRead < fileSize && cancelled != true){
		DEBUG(wxString::Format(_T("read(%d) < file size(%d)"), fileRead, fileSize));
		///session->OnFileTransferFailed(cookie);
	}
}
